package com.tsfyun.scm.log.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.tsfyun.common.base.constant.BaseContextConstant;
import com.tsfyun.common.base.dto.PaginationDto;
import com.tsfyun.common.base.util.DeviceUtil;
import com.tsfyun.common.base.util.TypeUtils;
import com.tsfyun.scm.log.model.SysLog;
import com.tsfyun.scm.log.util.TraceUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.slf4j.MDC;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @CreateDate: Created in 2020/9/16 14:27
 */
@Slf4j
@Aspect
@Order(-1)
public class LogAspect {

    @Value("${spring.application.name}")
    private String applicationName;

    private final String point = "execution(* com.tsfyun.*.controller..*.*(..)) || execution(* com.tsfyun.*.web..*.*(..))";

    private AntPathMatcher pathMatcher = new AntPathMatcher();

    private ThreadLocal<SysLog> sysLogThreadLocal = ThreadLocal.withInitial(() -> new SysLog());

    @Autowired
    private NoPrintProperties noPrintProperties;

    /**
     * 不打印请求日志的地址，后期改增加到配置中
     */
    private static List<String> noPrint = new ArrayList<String>(){{
       add("/taskNotice/count");
       add("/**/list");
       add("/**/page");
       add("/**/pageList");
       add("/splitOrder/**");
       add("/trustOrder/adminPage");
       add("/declaration/detail");
    }};

    @Before(point)
    public void beforeLog(JoinPoint joinPoint) {
        SysLog sysLog = sysLogThreadLocal.get();
        String traceId = TypeUtils.castToString(TraceUtil.getTrace(), MDC.get(BaseContextConstant.LOG_TRACE_ID));
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        sysLog.setTraceId(traceId);
        sysLog.setActionMethod(joinPoint.getTarget().getClass().getName().concat(joinPoint.getSignature().getName()));
        sysLog.setModule(applicationName);
        if(Objects.nonNull(request)) {
            sysLog.setRequestIp(ServletUtil.getClientIP(request));
            sysLog.setRequestUrl(URLUtil.getPath(request.getRequestURI()));
            sysLog.setHttpMethod(request.getMethod());
            sysLog.setDevice(DeviceUtil.getPlatform().getCode());
        }
        Object[] args = joinPoint.getArgs();
        List<Object> httpReqArgs = new ArrayList<>();
        for (Object object : args) {
            //文件参数忽略
            if (object instanceof HttpServletRequest || object instanceof HttpServletResponse
                    || object instanceof MultipartFile || object instanceof PaginationDto) {
            } else {
                httpReqArgs.add(object);
            }
        }
        try {
            String params = JSONObject.toJSONString(httpReqArgs);
            sysLog.setParams(params);
        } catch (Exception e) {
            log.error("解析请求参数异常",e);
        }
        sysLog.setStartTime(LocalDateTime.now());
    }

    @AfterReturning(pointcut = point,returning = "result")
    public void logAfter(Object result){
        SysLog sysLog = sysLogThreadLocal.get();
        sysLog.setEndTime(LocalDateTime.now());
        sysLog.setExecTime(Duration.between(sysLog.getStartTime(),sysLog.getEndTime()).getSeconds());
        sysLog.setFlag(Boolean.TRUE);
        Boolean isPrint = (CollUtil.isEmpty(noPrintProperties.getNoPrints()) ? noPrint : noPrintProperties.getNoPrints()).stream().filter(url->pathMatcher.match(url,sysLog.getRequestUrl())).count() <= 0;
        if(isPrint) {
            log.info("请求成功, 日志跟踪id={}, 设备={}，IP={}，请求地址={}，请求参数={},耗时={}秒, 响应={}", sysLog.getTraceId(), sysLog.getDevice(), sysLog.getRequestIp(), sysLog.getRequestUrl(), sysLog.getParams(), sysLog.getExecTime(),
                    JSON.toJSONString(result));
        }
        sysLogThreadLocal.remove();
    }


    /**
     * 异常增强
     */
    @AfterThrowing(pointcut = point, throwing = "throwable")
    public void logAfterThrowing(Throwable throwable) {
        SysLog sysLog = sysLogThreadLocal.get();
        sysLog.setEndTime(LocalDateTime.now());
        sysLog.setExecTime(Duration.between(sysLog.getStartTime(),sysLog.getEndTime()).getSeconds());
        sysLog.setFlag(Boolean.FALSE);
        sysLog.setRemark(throwable.getMessage());
        log.error("请求异常，日志跟踪id={},设备={}，IP={}，请求地址={} , 请求参数={} ,耗时={}秒, 异常 ={} ", sysLog.getTraceId(), sysLog.getDevice(),sysLog.getRequestIp(),sysLog.getRequestUrl(), sysLog.getParams(),sysLog.getExecTime(),throwable.getMessage());
        sysLogThreadLocal.remove();
    }


}
